/*****************************************************************************
 *
 *  This code is used to communicate by USART interface.
 *
 *****************************************************************************/

/* Scheduler includes */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Project application includes */
#include "uart.h"

/* -------------------------------------------------------------------------- */



/* -------------------------------------------------------------------------- */

/* Queues used to hold received characters, and characters waiting to be transmitted */
static xQueueHandle xRxedChars;
static xQueueHandle xCharsForTx;
static volatile long lTHREEmpty;

/* Communication flag between the interrupt service routine and serial API */
static volatile long *plTHREEmpty;

void vUARTISRCreateQueues( unsigned portBASE_TYPE uxQueueLength, xQueueHandle *pxRxedChars, xQueueHandle *pxCharsForTx, long volatile **pplTHREEmptyFlag )
{
  /* Create the queues used to hold Rx and Tx characters */
  xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE) sizeof( signed char ) );
  xCharsForTx = xQueueCreate( uxQueueLength + 1, ( unsigned portBASE_TYPE) sizeof( signed char ) );

  /* Pass back reference to the queues so the serial API file can post/receive characters */
  *pxRxedChars = xRxedChars;
  *pxCharsForTx = xCharsForTx;

  /* Initialise the THRE empty flag - and pass back a reference */
  lTHREEmpty = ( long ) pdTRUE;
  *pplTHREEmptyFlag = &lTHREEmpty;
}

void vUARTInit(eBaud eWantedBaud, eParity eWantedParity, eDataBits eWantedDataBits, eStopBits eWantedStopBits, unsigned portBASE_TYPE uxBufferLength)
{
  unsigned long ulDivisor;

  vUARTISRCreateQueues( uxBufferLength, &xRxedChars, &xCharsForTx, &plTHREEmpty);

  if ( ( xRxedChars != uartINVALID_QUEUE ) && ( xCharsForTx != uartINVALID_QUEUE ) && ( eWantedBaud != ( unsigned long ) 0 ) )
    {
      portENTER_CRITICAL();
      {
        /* Setup the baudrate: calculate the divisor */
        ulDivisor = uartCLOCK_HZ / ( eWantedBaud * uartCLOCK_SCALING );

        /* Set the DLAB bit so we can access the divisor */
        LPC_UART1->LCR |= uartDLAB;

        /* Setup the divisor */
        LPC_UART1->DLL = ( unsigned char ) ( ulDivisor & ( unsigned long ) 0xff );
        ulDivisor >>= 8;
        LPC_UART1->DLM = ( unsigned char ) ( ulDivisor & ( unsigned long ) 0xff );

        /* Turn on the FIFO and clear the buffers */
        LPC_UART1->FCR = ( uartFIFO_ON | uartFIFO_CLEAR );

        /* Setup transmission format */
        LPC_UART1->LCR = (eWantedDataBits) | (eWantedStopBits << 2) | (eWantedParity << 3);

        /* Setup the NVIC for the UART1 */
        NVIC_EnableIRQ ( UART1_IRQn );
        LPC_UART1->IER |= uartTHRE_EN;
      }
      portEXIT_CRITICAL();
    }
}

signed portBASE_TYPE xUARTGetChar( signed char *pcRxedChar, portTickType xBlockTime )
{
  /* Get the next character from the buffer.  Return false if no characters are available, or arrive before xBlockTime expires. */
  if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
    {
      return pdTRUE;
    }
  else
    {
      return pdFALSE;
    }
}

signed portBASE_TYPE xUARTPutChar ( signed char cOutChar, portTickType xBlockTime )
{
signed portBASE_TYPE xReturn;

  portENTER_CRITICAL();
  {
    /* Is there space to write directly to the UART? */
    if( *plTHREEmpty == ( long ) pdTRUE )
      {
        /* We wrote the character directly to the UART, so was succesful */
        *plTHREEmpty = pdFALSE;
        LPC_UART1->THR = cOutChar;
        xReturn = pdPASS;
      }
    else
      {
        /* We cannot write directly to the UART, so queue the character. Block
         * for a maximum of xBlockTime if there is no space in the queue */
        xReturn = xQueueSend ( xCharsForTx, &cOutChar, xBlockTime );

        /* Depending on queue sizing and task prioritisation:
         * While we were blocked waiting to post interrupts were not disabled.
         * It is possible that the serial ISR has emptied the Tx queue, in which
         * case we need to start Tx off again */
        if ( ( *plTHREEmpty == ( long ) pdTRUE ) && ( xReturn == pdPASS ) )
          {
            xQueueReceive ( xCharsForTx, &cOutChar, xBlockTime );
            *plTHREEmpty = pdFALSE;
            LPC_UART1->THR = cOutChar;
          }
      }
  }
  portEXIT_CRITICAL();

  return xReturn;
}

void vUARTPutString ( const signed char * const pcString, unsigned short usStringLength )
{
signed char *pxNext;

  ( void ) usStringLength;

  /* Send each character in the string, one at the time */
  pxNext = ( signed char  * ) pcString;
  while ( *pxNext )
    {
      xUARTPutChar ( *pxNext, uartNO_BLOCK );
      pxNext++;
    }
}

void UART1_IRQHandler ( void )
{
signed char cChar;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

  /* What caused the interrupt? */
  switch ( LPC_UART1->IIR & uartINTERRUPT_SOURCE_MASK )
  {
  case uartSOURCE_ERROR: /* Not handling this, but clear interrupt */
    cChar = LPC_UART1->LSR;
    break;
  case uartSOURCE_THRE: /* The THRE is empty. If there is another character in the Tx queue, sent it now */
    if ( xQueueReceiveFromISR ( xCharsForTx, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE )
      {
        LPC_UART1->THR = cChar;
      }
    else
      {
        /* There are no further characters queued to send so we can indicate that the THRE is available */
        lTHREEmpty = pdTRUE;
      }
    break;
  case uartSOURCE_RX_TIMEOUT:
  case uartSOURCE_RX: /* A character was received. Place it in the queue of received characters */
    cChar = LPC_UART1->RBR;
    xQueueSendFromISR ( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
    break;
  default: /* There is nothing to do, leave the ISR */
    break;
  }
  if ( xHigherPriorityTaskWoken )
    {
      portYIELD();
    }
}
